//
//  TabFile.cpp
//  germ
//
//  TabFile.cpp
//  Created by Rodger Voelker
//  Purpose: class representing tab delimited sequence database file
//  
//

#include "TabFile.h"
#include <algorithm>
#include <iomanip>
#include <ctime>
#include <sstream>

using namespace std;

TabFile::tMode TabFile::read = 0;
TabFile::tMode TabFile::write = 1;
//---------------- constructors / destructor --------------------------
//Note: I'm just initializing the file name to something that could never represent an actual filename
TabFile::TabFile(void): _masterfile_name("*"), _nfields(0), _nrows(0), _data_start(0),
_saved_pos(0), _saved_mode(ios::in), _end_last_read(0), _headerstring(""), _isModified(false){
}

//destructor
TabFile::~TabFile(){
	if( _masterFH.is_open() ){
		_masterFH.close();
	}
}

//------------- open ------------------------
//associate object with file by file name and initialize
bool TabFile::open( string file_name ){ //assume has header
	if(!file_name.size()){
		cerr << "ERROR opening TabFile object: file_name is empty." << endl;
		return false;
	}
	//initialize object
	_masterfile_name = file_name;
	bool status = initialize();
	return status;
}

bool TabFile::open( string file_name, bool hasHeader ){
	if(!file_name.size()){
		cerr << "ERROR opening TabFile object: file_name is empty." << endl;
		return false;
	}
	//initialize object
	_masterfile_name = file_name;
	bool status = initialize(hasHeader);
	return status;
}

bool TabFile::open( string file_name, TabFile::tMode mode ){ //used to create or append to file
	if(!file_name.size()){
		cerr << "ERROR opening TabFile object: file_name is empty." << endl;
		return false;
	}
	_masterfile_name = file_name;
	if(mode == read){
		//initialize object
		return initialize();
	}else if(mode == write){
		return _openFileForAppend();
	}else{
		cerr << "ERROR opening TabFile object: mode type passed not supported." << endl;
		cerr << "    Mode must be either: TabFile::read or TabFile::write. " << endl;
		return false;
	}
}


//----------------close() -------------------------------
//close file and destroy object
void TabFile::close(void){
	if( _masterFH.is_open() ){
		_masterFH.close();
	}
  //	TabFile::~TabFile();
}

//------------------- clear() -----------------------------
//clears the file!!!!
bool TabFile::clearDBFile(void){
	if( !_openFileForWrite() ){
		cerr << "IN: bool TabFile::clearDBFile(void);" << endl;
		cerr << "ERROR: could not open filestream. " << endl;
		return false;
	}
	_isModified = true;
	return true;
}

//------------- initialize() ------------------------
//open filestream to ios::in by default
bool TabFile::initialize(void){ //assume has header
	if( !_openFileForRead() ){
		//cerr << "IN: bool TabFile::_initialize(void); " << endl;
		//cerr << "ERROR: could not open filestream. " << endl;
		return false;
	}
	fp_record_data_start(true);
	_header = getHeader( );
	_nfields = _header.size();
	_headerprinted = false;
  //	_end_last_read = _data_start;
	return true;
}

bool TabFile::initialize(bool hasHeader){
	if( !_openFileForRead() ){
		//cerr << "IN: bool TabFile::_initialize(void); " << endl;
		//cerr << "ERROR: could not open filestream. " << endl;
		return false;
	}
	fp_record_data_start(hasHeader);
	if(hasHeader){
		_header = getHeader( );
	}
	_nfields = _header.size();
	_headerprinted = false;
  //	_end_last_read = _data_start;
	return true;
}

//-------------- reset() ----------------------
void TabFile::reset(void){
	this->close();
	this->open(_masterfile_name);
}

//-------------- eof() --------------------------
bool TabFile::eof(void){
	return _masterFH.eof();
}

//-------------  fp_tell() ------------------------
//report current file position
streampos TabFile::fp_tell(void){
	if( _masterFH.is_open() ){
		return _masterFH.tellg();
	}else{
		cerr << "In: streampos TabFile::tell(): ERROR: file stream not open. " << endl;
		return 0;
	}
}

//-------------  fp_seek() ------------------------
//set file pointer to specified position
bool TabFile::fp_seek( streampos file_pos ){
	if( _masterFH.is_open() ){
		_masterFH.clear();
		_masterFH.seekg(file_pos, std::ios::beg);
		return true;
	}else{
		cerr << "In: bool TabFile::fp_seek( streampos file_pos ): ERROR: file stream not open. " << endl;
		exit(0);
	}
}

//-------------  fp_start() ------------------------
bool TabFile::fp_start( ){
	bool success = fp_seek( 0 );
	return success;
}

//-------------  fp_data_start() ------------------------
bool TabFile::fp_data_start( ){
	bool success = fp_seek( _data_start );
	return success;
}


//-------------  nRows() ------------------------
unsigned long TabFile::nRows(){
	if( !_nrows || _isModified ){ //only determine if needed
    //	_fp_record();
		if( !_canRead() ){
			if( !_openFileForRead() ){
				cerr << "In: unsigned long TabFile::nRows(): " << endl;
				cerr << "ERROR: could not open filestream." << endl;
				exit(0);
			}
		}
		_masterFH.seekg( _data_start );
		string buffer;
		while( std::getline( _masterFH, buffer, '\n') ){
			++_nrows;
		}
		_isModified = false;
    //	_fp_restore();
	}
	return( _nrows );
}

//-------------  getNextRow() Overloaded ------------------------
//get a single line as a string, if success then return true, else false
unsigned TabFile::getNextRow(string& line){
	//_fp_record();
	line.clear();
	if( !_canRead()){
		if( !_openFileForRead() ){
			cerr << "In: bool TabFile::getNextRow(string& line): " << endl;
			cerr << "ERROR: could not open filestream." << endl;
			exit(0);
		}
	}
	//set fp to last read and read next line, advance read fp
  //	_masterFH.seekg( _end_last_read );
	if( std::getline(_masterFH, line, '\n') ){
    //		_end_last_read = _masterFH.tellg();
		return 1;
	}else{
		return 0;
	}
}

//get at most max_lines at a time, assign to 'lines'
unsigned TabFile::getNextRow(typeStrings& lines, unsigned max_lines){
	lines.clear();
	if( max_lines < 1){
		cerr << "In: int TabFile::getNextRow(typeStrings& lines, int max_lines): " << endl;
		cerr << "ERROR: max lines to be read must be greater than or equal to 1" << endl;
		exit(0);
	}
	if( !_canRead() ){
		if( !_openFileForRead() ){
			cerr << "In: int TabFile::getNextRow(typeStrings& lines, int max_lines): " << endl;
			cerr << "ERROR: could not open filestream." << endl;
			exit(0);
		}
	}
  //	_masterFH.seekg( _end_last_read );
	string buffer;
	unsigned lines_read = 0;
	while( lines_read < max_lines && std::getline( _masterFH, buffer, '\n') ){
		lines.push_back(buffer);
    //		_end_last_read = _masterFH.tellg();
		++lines_read;
	}
	return( lines_read );
}

//read entire file into vector
unsigned TabFile::getNextRow(typeStrings& data_vec){
	data_vec.clear();
	if( !_canRead() ){
		if( !_openFileForRead() ){
			cerr << "In: unsigned long TabFile::getNextRow(tyepRow& lines): " << endl;
			cerr << "ERROR: could not open filestream." << endl;
			exit(0);
		}
	}
  //	_masterFH.seekg( _end_last_read );
	unsigned lines_read = 0;
	string buffer;
	while( std::getline( _masterFH, buffer,'\n') ){
		data_vec.push_back(buffer);
		buffer.clear();
    //		_end_last_read = _masterFH.tellg();
		++lines_read;
	}
	return(lines_read );
}

//-------------  getField() Overloaded------------------------
//Parses next line into vector using tab delimiters and advances file pointer
bool TabFile::getField(typeStrings& data_out){
	data_out.clear();
	if( !_canRead() ){
		if( !_openFileForRead() ){
			cerr << "In: bool TabFile::getField(typeStrings& data_out): " << endl;
			cerr << "ERROR: could not open filestream." << endl;
			exit(0);
		}
	}
  
  //	_masterFH.seekg( _end_last_read );
	string buffer;
	if( std::getline( _masterFH, buffer) ){
		_split_tab(buffer, data_out);
    //		_end_last_read = _masterFH.tellg();
		return true;
	}else{
		return false;
	}
}

//Parses next line into vector using tab delimiters and advances file pointer, also store the line intact in buffer
bool TabFile::getField(typeStrings& data_out, string& buffer){
	data_out.clear();
	buffer.clear();
	if( !_canRead() ){
		if( !_openFileForRead() ){
			cerr << "In: bool TabFile::getField(typeStrings& data_out): " << endl;
			cerr << "ERROR: could not open filestream." << endl;
			exit(0);
		}
	}
  //	_masterFH.seekg( _end_last_read );
	if( std::getline( _masterFH, buffer) ){
		_split_tab(buffer, data_out);
    //		_end_last_read = _masterFH.tellg();
		return true;
	}else{
		return false;
	}
}

//Parses next line into vector but only retrieves specified fields (1st field = 0) and advances file pointer
bool TabFile::getField( typeStrings& data_out, vector<int>& fields){
	data_out.clear();
	if( !_canRead() ){
		if( !_openFileForRead() ){
			cerr << "In: bool TabFile::getField( typeStrings& data_out, vector<unsigned>& fields): " << endl;
			cerr << "ERROR: could not open filestream." << endl;
			exit(0);
		}
	}
	if( *(std::min_element(fields.begin(), fields.end())) < 0 ){
		cerr << "In: bool TabFile::getField( typeStrings& data_out, vector<unsigned>& fields): " << endl;
		cerr << "ERROR: in field request: minimum field number available for request is 0. " << endl;
		cerr << "The min requested was: " << *(std::min_element(fields.begin(), fields.end())) << endl;
		exit(0);
	}
	if( *(std::max_element(fields.begin(), fields.end())) >= _nfields ){
		cerr << "In: bool TabFile::getField( typeStrings& data_out, vector<unsigned>& fields): " << endl;
		cerr << "The max field number available is: " << (_nfields - 1) << endl;
		cerr << "The max requested was: " << *(std::max_element(fields.begin(), fields.end())) << endl;
		exit(0);
	}
  //	_masterFH.seekg( _end_last_read );
	string str_buf;
	vector<string> vec_buf;
	if( std::getline( _masterFH, str_buf) ){
    //		_end_last_read = _masterFH.tellg();
		_split_tab(str_buf, vec_buf);
		for(vector<int>::iterator field_it = fields.begin(); field_it != fields.end(); ++field_it){
			data_out.push_back( vec_buf[*field_it] );
		}
		return true;
	}else{
		return false;
	}
}

//Parses next line and retrieves only specified field and advances file pointer
bool TabFile::getField( string& data_out, int field){
	data_out.clear();
	if( field < 0 ){
		cerr << "In: bool TabFile::getField( string& data_out, unsigned field): " << endl;
		cerr << "ERROR: in field request: minimum field number available for request is 0. " << endl;
		exit(0);
	}
	if( field >= _nfields){
		cerr << "In: bool TabFile::getField( string& data_out, unsigned field): " << endl;
		cerr << "ERROR: in field request: the maximum field available is: " << _nfields-1 << endl;
		exit(0);
	}
	if( !_canRead() ){
		if( !_openFileForRead() ){
			cerr << "In: bool TabFile::getField( string& data_out, unsigned field): " << endl;
			cerr << "ERROR: could not open filestream." << endl;
			exit(0);
		}
	}
  //	_masterFH.seekg( _end_last_read );
	string str_buf;
	vector<string> vec_buf;
	if( std::getline(_masterFH, str_buf) ){
    //		_end_last_read = _masterFH.tellg();
		_split_tab(str_buf, vec_buf);
		data_out = vec_buf[field];
		return true;
	}else{
		return false;
	}
}

//-------------  addRow() Overloaded ------------------------
//append data to end of file, add a single line
bool TabFile::addRow( typeStrings& new_data ){
	if( !_canAppend() ){
		if( !_openFileForAppend() ){
			cerr << "In: bool TabFile::addRow( typeStrings& new_data ): " << endl;
			cerr << "ERROR: could not open filestream for appending." << endl;
			return false;
		}
	}
	unsigned new_nfields = new_data.size();
  //	if( new_nfields != _nfields ){
  //		cerr << "In: bool TabFile::addRow( typeStrings& new_data ): " << endl;
  //		cerr << "Warning: field mismatch between new data and existing file. " << endl;
  //	}
	//write the data
	for( unsigned i = 0; i< new_nfields-1; ++i ){
		_masterFH << new_data[i] << "\t" ;
	}
	_masterFH << new_data[ new_nfields-1 ] << endl;
	_isModified = true;
	return true;
}

//append data to end of file, add a set of lines
bool TabFile::addRow( typeDBase& new_data ){
	if( !_canAppend() ){
		if( !_openFileForAppend() ){
			cerr << "In: bool TabFile::addRow( typeDBase& new_data ): " << endl;
			cerr << "ERROR: could not open filestream for appending." << endl;
			return false;
		}
	}
	for(typeDBase::iterator dbIt = new_data.begin(); dbIt != new_data.end(); ++dbIt ){
		unsigned new_nfields = dbIt->size();
    //	if( new_nfields != _nfields ){
    //		cerr << "In: bool TabFile::addRow( typeDBase& new_data ): " << endl;
    //		cerr << "Warning: field mismatch between new data and existing file. " << endl;
    //	}
		//write the data
		for( unsigned i = 0; i< new_nfields-1; ++i ){
			_masterFH << (*dbIt)[i] << "\t" ;
		}
		_masterFH << (*dbIt)[ new_nfields-1 ] << endl;
	}
	_isModified = true;
	return true;
}


bool TabFile::addRow( stringstream& new_data ){
	if( !_canAppend() ){
		if( !_openFileForAppend() ){
			cerr << "In: bool TabFile::addRow( stringstream& new_data ): " << endl;
			cerr << "ERROR: could not open filestream for appending." << endl;
			return false;
		}
	}
	_masterFH << new_data.str() << endl;
	_isModified = true;
	return true;
}

//-------------  print() ------------------------
void TabFile::print( ostream& the_stream ){
	// _fp_record();
	if( !_canRead() ){
		if( !_openFileForRead() ){
			cerr << "In: void TabFile::print( ostream& the_stream ): " << endl;
			cerr << "ERROR: could not open filestream." << endl;
			exit(0);
		}
	}
	fp_seek( std::ios::beg );
	string buffer;
	while( std::getline( _masterFH, buffer, '\n') ){
		the_stream << buffer << endl;
	}
  //	 _masterFH.seekg(  _saved_pos );
}

//-------------  getHeader() ------------------------
//Parses header line into vector using tab delimiters
TabFile::typeStrings TabFile::getHeader(void){
	//_fp_record();
	if( !_canRead() ){
		if( !_openFileForRead() ){
			cerr << "In: typeStrings TabFile::getHeader(void): " << endl;
			cerr << "ERROR: could not open filestream." << endl;
			exit(0);
		}
	}
	fp_seek( std::ios::beg );
	//_masterFH.seekg( std::ios::beg);
	typeStrings headerVec;
	std::getline( _masterFH, _headerstring);
	_split_tab(_headerstring, headerVec);
	return headerVec;
}

//------------------ setHeader ------------------
bool TabFile::setHeader(vector<string>& fields){
	if(!fields.size()){
		cerr << "ERROR setting header for TabFile object. No fields were passed." << endl;
		return false;
	}
	_header.clear();
	_headerstring = "";
	unsigned i=0;
	_headerstring += fields[i];
	_header.push_back(fields[i]);
	++i;
	for(; i< fields.size(); ++i){
		_header.push_back(fields[i]);
		_headerstring += ("\t" + fields[i]);
	}
	_nfields = _header.size();
	return true;
}

//---------------- writeHeader -------------------
//write header to file
bool TabFile::writeHeader(void){
	if(!addRow(_header)){
		cerr << "ERROR writing header to TabFile object." << endl;
		return false;
	}
	return true;
}

//------------- printHeaderFormatted() ------------------------
//print the header using the specified number of columns
void TabFile::printHeaderFormatted( ostream& stream, int columns ){
	if(columns < 1){
		cerr << "ERROR in printHeaderFormatted. Number of columns specified must be >= 1." << endl;
		return;
	}
	int rows = _nfields / columns;
	if( _nfields % columns ){
		rows += 1;
	}
	vector<string>::iterator strIt = _header.begin();
	int position;
	for(int r = 1; r <= rows; ++r){
		for(int c = 0; c < columns; ++c){
			position = r + (rows*c) -1; //the -1 adjusts for the 0 array offset
			if (position < _nfields){
				stream << " [" << setw(2) << left << position+1 << "] " << setw(26) << left << *(strIt + position);
			}
		}
		stream << endl;
	}
	_headerprinted = true;
}

//------------------------- getFieldIndex ---------------------------------
int TabFile::getFieldIndex( string query_message ){
	if(!_header.size()){
		cerr << "ERROR in TabFile::getFieldIndex. Getting field index from user has not been implemented for headerless files yet." << endl;
		exit(1);
	}
	if(!_headerprinted){
		cout << endl << "************* Fields found in file=" << _masterfile_name << " *****************" << endl;
		printHeaderFormatted(cout, 5);
		_headerprinted = true;
	}
	int field = io.getIntWithBounds(query_message, 1, _nfields);
	--field;
	return field;
}

int TabFile::getFieldIndex( string query_message, int user_field ){
	if(!_header.size()){
		cerr << "ERROR in TabFile::getFieldIndex. Getting field index from user has not been implemented for headerless files yet." << endl;
		exit(1);
	}
	int field = user_field;
	if( field < 1 || field > _nfields ){
		if(!_headerprinted){
			cout << endl << "************* Fields found in file=" << _masterfile_name << " *****************" << endl;
			printHeaderFormatted(cout, 5);
			_headerprinted = true;
		}
		field = io.getIntWithBounds(query_message, 1, _nfields);
	}
	--field;
	return field;
}

//-------------  _verifyRange() ------------------------
bool TabFile::verifyRange( int field ){
	if( field >= 0 && field <  _nfields ){
		return true;
	}else{
		cerr << "In: bool TabFile::_verifyRange( unsigned field ); " << endl;
		cerr << "ERROR: requested field is out of range. " << endl;
		return false;
	}
}

//-------------  _fp_record_data_start() ------------------------
//save the position of the start of the first line after the header = data_start
bool TabFile::fp_record_data_start(bool hasHeader){
	//_fp_record();
	if( !_canRead() ){
		if( !_openFileForRead() ){
			cerr << "IN: bool TabFile::fp_record_data_start(); " << endl;
			cerr << "ERROR: could not open filestream. " << endl;
			exit(0);
		}
	}
	_masterFH.clear();
	_masterFH.seekg(std::ios::beg);
	if(hasHeader){
		std::getline( _masterFH, _headerstring, '\n'); //move past the header
	}else{
		_headerstring = "";
	}
  _data_start =  _masterFH.tellg();
	//_fp_restore();
	return true;
}

//-------------  _fp_record() ------------------------
//Note: the calling function needs to make sure the filestream exists
void TabFile::_fp_record(void){ //set internal reference, used by other member functions
  _saved_pos =  _masterFH.tellg();
  _saved_mode = _masterFH_mode;
}

//-------------  _fp_restore() ------------------------
void TabFile::_fp_restore(void){//set file pointer to _saved_pos, used by member functions
  _masterFH.clear();
  if( _saved_mode != _masterFH_mode ){
		_masterFH.close();
	 	_masterFH.open( _masterfile_name.c_str(), _saved_mode );
  }
  _masterFH.seekg(  _saved_pos );
}


//-------------  _split_tab() ------------------------
//split line on tabs and put into vector, if field is empty it returns an empty field (ie maintains number of fields)
/*
 unsigned TabFile::_split_tab(string& instring, vector<string>& outvec){
 unsigned n_tabs = 0;
 unsigned last = instring.size();
 string data = "";
 for( unsigned pos=0; pos < last; ++pos ){
 char c = instring.at(pos);
 if( c == '\t' ){
 ++n_tabs;
 outvec.push_back(data);
 data = "";
 }else{
 data += c;
 }
 }
 outvec.push_back(data);
 return n_tabs-1;
 }
 */
unsigned TabFile::_split_tab(string& instring, vector<string>& outvec){
	size_t lastpos=0;
	size_t posfound=0;
	while( (posfound= instring.find('\t', lastpos)) != string::npos){
		outvec.push_back(instring.substr(lastpos, posfound - lastpos));
		lastpos = posfound +1;
	}
	outvec.push_back(instring.substr(lastpos));
	return outvec.size();
}




//----------------------- _openFileForRead() ------------------------------------
//calling function is responsible for saving previous state
bool TabFile::_openFileForRead(void){
	if( _masterFH.is_open() ){
		_masterFH.close();
	}
	_masterFH.clear();
	_masterFH_mode = ios::in;
	_masterFH.open( _masterfile_name.c_str(), _masterFH_mode );
  
	if( _masterFH.is_open() ){
		return true;
	}else{
		//cerr << "IN: bool TabFile::_openFileForRead(void); " << endl;
		//cerr << "ERROR: could not open filestream for reading. " << endl;
		return false;
	}
}

//----------------------- _openFileForWrite() ------------------------------------
//calling function is responsible for saving previous state
bool TabFile::_openFileForWrite(void){
	if( _masterFH.is_open() ){
		_masterFH.close();
	}
	_masterFH.clear();
	_masterFH_mode = ios::out|ios::trunc;
	_masterFH.open( _masterfile_name.c_str(), _masterFH_mode );
  
	if( _masterFH.is_open() ){
		return true;
	}else{
		cerr << "IN: bool TabFile::_openFileForWrite(void); " << endl;
		cerr << "ERROR: could not open filestream for write with truncate. " << endl;
		return false;
	}
}

//----------------------- _openFileForAppend() ------------------------------------
//calling function is responsible for saving previous state
bool TabFile::_openFileForAppend(void){
	if( _masterFH.is_open() ){
		_masterFH.close();
	}
	_masterFH.clear();
	_masterFH_mode = ios::out|ios::app;
	_masterFH.open( _masterfile_name.c_str(), _masterFH_mode );
  
	if( _masterFH.is_open() ){
		return true;
	}else{
		cerr << "IN: bool TabFile::_openFileForAppend(void); " << endl;
		cerr << "ERROR: could not open filestream for write with append. " << endl;
		return false;
	}
}

//----------------------- _canRead() ------------------------------------
bool TabFile::_canRead(void){
	_masterFH.clear();
	if( _masterFH_mode == static_cast<ios::openmode>(ios::in) ){
		return true;
	}else{
		return false;
	}
}

//----------------------- _canWrite() ------------------------------------
bool TabFile::_canWrite(void){
	_masterFH.clear();
	if( _masterFH_mode == static_cast<ios::openmode>(ios::out|ios::trunc) ){
		return true;
	}else{
		return false;
	}
}

//----------------------- _canAppend() ------------------------------------
bool TabFile::_canAppend(void){
	_masterFH.clear();
	if( _masterFH_mode == static_cast<ios::openmode>(ios::out|ios::app) ){
		return true;
	}else{
		return false;
	}
}
